/**
 * 
 */
package jm.lib.collections.map;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * 
 * @author Jiming Liu
 *
 */
public class TinyMap<K,V> implements Map<K,V>, Cloneable, Serializable{

	/**
	 * 
	 */
	private static final long serialVersionUID = -1457437173053870345L;
	
	private int size;
	SimpleMapEntry<K,V>[] entrys;
	
	public TinyMap() {
		this(6);
	}
	
	@SuppressWarnings("unchecked")
	public TinyMap(int initialCapacity) {
		entrys = new SimpleMapEntry[Math.max(2, initialCapacity)];
	}
	
	/* (non-Javadoc)
	 * @see java.util.Map#size()
	 */
	public int size() {
		return size;
	}

	/* (non-Javadoc)
	 * @see java.util.Map#isEmpty()
	 */
	public boolean isEmpty() {
		return 0 == size;
	}

	/* (non-Javadoc)
	 * @see java.util.Map#containsKey(java.lang.Object)
	 */
	public boolean containsKey(Object key) {
		if(0 == size) return false;
		for (int i = 0; i < size; i++) {
			if(entrys[i].getKey().equals(key)) return true;
		}
		return false;
	}

	/* (non-Javadoc)
	 * @see java.util.Map#containsValue(java.lang.Object)
	 */
	public boolean containsValue(Object value) {
		if(0 == size) return false;
		for (int i = 0; i < size; i++) {
			if(entrys[i].getValue().equals(value)) return true;
		}
		return false;
	}

	/* (non-Javadoc)
	 * @see java.util.Map#get(java.lang.Object)
	 */
	public V get(Object key) {
		if(0 == size) return null;
		for (int i = 0; i < size; i++) {
			if(entrys[i].getKey().equals(key)) return entrys[i].getValue();
		}
		return null;
	}

	/* (non-Javadoc)
	 * @see java.util.Map#put(K, V)
	 */
	@SuppressWarnings("unchecked")
	public V put(K key, V value) {
		V result = null;
		for (int i = 0; i < size; i++) {
			if(entrys[i].getKey().equals(key)) {
				result = entrys[i].getValue();
				entrys[i].setValue(value);
				return result;
			}
		}
		SimpleMapEntry<K,V> entry = new SimpleMapEntry<K,V>(key);
		entry.setValue(value);
		if(size < entrys.length) {
			entrys[size++] = entry;
		} else {
			SimpleMapEntry<K,V>[] oldEntries = entrys;
			SimpleMapEntry<K,V>[] newEntries = new SimpleMapEntry[size*2];
			System.arraycopy(oldEntries, 0, newEntries, 0, size);
			newEntries[size++] = entry;
			entrys = newEntries;
		}
		return result;
	}

	/* (non-Javadoc)
	 * @see java.util.Map#remove(java.lang.Object)
	 */
	public V remove(Object key) {
		V result = null;
		for (int i = 0; i < size; i++) {
			if(entrys[i].getKey().equals(key)) {
				result = entrys[i].getValue();
				if(i < size-1) {
					System.arraycopy(entrys, i+1, entrys, i, size - i - 1);
				}
				entrys[--size] = null;
				return result;
			}
		}
		return result;
	}

	/* (non-Javadoc)
	 * @see java.util.Map#putAll(java.util.Map)
	 */
	public void putAll(Map<? extends K, ? extends V> m) {
		for(Entry<? extends K, ? extends V> e : m.entrySet()) {
			put(e.getKey(),e.getValue());
		}
	}

	/* (non-Javadoc)
	 * @see java.util.Map#clear()
	 */
	public void clear() {
		for (int i = 0; i < size; i++) {
			entrys[i] = null;
		}
		size = 0;
	}

	/* (non-Javadoc)
	 * @see java.util.Map#keySet()
	 */
	public Set<K> keySet() {
		Set<K> result = new LinkedHashSet<K>();
		for (int i = 0; i < size; i++) {
			result.add(entrys[i].getKey());
		}
		return result;
	}

	/* (non-Javadoc)
	 * @see java.util.Map#values()
	 */
	public Collection<V> values() {
		List<V> result = new ArrayList<V>(size);
		for (int i = 0; i < size; i++) {
			result.add(entrys[i].getValue());
		}
		return result;
	}

	/* (non-Javadoc)
	 * @see java.util.Map#entrySet()
	 */
	public Set<Entry<K,V>> entrySet() {
		Set<Entry<K,V>> result = new LinkedHashSet<Entry<K,V>>();
		for (int i = 0; i < size; i++) {
			result.add(entrys[i]);
		}
		return result;
	}

	/**
	 * Override hashCode.
	 *
	 * @return the Objects hashcode.
	 */
	public int hashCode() {
		int hashCode = 1;
		hashCode = 31 * hashCode + size;
		for (int i0 = 0; entrys != null && i0 < entrys.length; i0++) {
			hashCode = 31
				* hashCode
				+ (entrys == null ? 0 : entrys[i0].hashCode());
		}
		return hashCode;
	}

	/**
	 * Returns <code>true</code> if this <code>TinyMap</code> is the same as the o argument.
	 *
	 * @return <code>true</code> if this <code>TinyMap</code> is the same as the o argument.
	 */
	public boolean equals(Object o) {
		if (this == o) {
			return true;
		}
		if (o == null) {
			return false;
		}
		if (o.getClass() != getClass()) {
			return false;
		}
		TinyMap m = (TinyMap) o;
		if(m.size() != size) return false;
        try {
        	for (int i = 0; i < size; i++) {
        		Entry<K,V> e = entrys[i];
        		K key = e.getKey();
                V value = e.getValue();
                if (value == null) {
                    if (!(m.get(key)==null && m.containsKey(key)))
                        return false;
                } else {
                    if (!value.equals(m.get(key)))
                        return false;
                }
			}
        } catch (ClassCastException unused) {
            return false;
        } catch (NullPointerException unused) {
            return false;
        }
        return true;
	}


	
	public String toString() {
		StringBuffer buffer = new StringBuffer();
		buffer.append("[TinyMap:");
		buffer.append(" size: ");
		buffer.append(size);
		buffer.append(" { ");
		for (int i0 = 0; entrys != null && i0 < size; i0++) {
			buffer.append(" entrys[" + i0 + "]: ");
			buffer.append(entrys[i0]);
		}
		buffer.append(" } ");
		buffer.append("]");
		return buffer.toString();
	}
	
	public int getBufferLength() {
		return entrys.length;
	}

	@SuppressWarnings("unchecked")
	public Object clone() {
		TinyMap inst = new TinyMap();
		inst.size = this.size;
		if (this.entrys != null) {
			inst.entrys = new SimpleMapEntry[size];
			for (int i0 = 0; i0 < size; i0++) {
				inst.entrys[i0] = (SimpleMapEntry)this.entrys[i0].clone();
			}
		} 
		return inst;
	}

}
